/*
 * Jamus
 * Copyright (C) 2010 Jamus Team
 *
 * Jamus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jamus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jamus.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <boost/foreach.hpp>

#include "Plugin.h"
#include "../../NoCurrentTrackException.h"
#include "../../InvalidPlaybackStateException.h"

using namespace std;

namespace backend {
namespace stub {

Plugin::Plugin()
: playbackStatus(STOP),
  currentTrack(-1),
  volume(50)
{
}

Plugin::Plugin(const TrackList& tracks, const Playlist& playlist, Plugin::PlaybackStatus playbackStatus, int volume, int currentTrack)
: tracks(tracks),
  playlist(playlist),
  playbackStatus(playbackStatus),
  currentTrack(currentTrack),
  volume(volume),
  running(true)
{
}

Plugin::~Plugin()
{
}

void Plugin::run()
{
	while(running) {
		boost::unique_lock<boost::mutex> lock(mutex);

		while(signalsToFire.empty() && running)
			fireSignals.wait(lock);

		// Fire signals
		BOOST_FOREACH(boost::signals2::signal<void()>* s, signalsToFire)
			(*s)();

		signalsToFire.clear();
	}
}

void Plugin::cancel()
{
	boost::unique_lock<boost::mutex> lock(mutex);

	running = false;
	fireSignals.notify_one();
}

Plugin::PlaybackStatus Plugin::getPlaybackStatus()
{
	return playbackStatus;
}

void Plugin::play()
{
	if(playbackStatus == PLAY)
		return;

	if(currentTrack < 0)
		throw NoCurrentTrackException();

	playbackStatus = PLAY;

	scheduleSignal(sigPlaybackStatusChanged);
}

void Plugin::play(int track)
{
	if(track < 0 || static_cast<size_t>(track) >= playlist.size())
		throw runtime_error("Invalid track number");

	currentTrack = track;

	playbackStatus = PLAY;
	resetElapsed();

	scheduleSignal(sigPlaybackStatusChanged);
}

void Plugin::pause()
{
	if(currentTrack < 0)
		throw NoCurrentTrackException();

	if(playbackStatus != PLAY)
		throw InvalidPlaybackStateException();

	playbackStatus = PAUSE;

	scheduleSignal(sigPlaybackStatusChanged);
}

void Plugin::stop()
{
	if(playbackStatus == STOP)
		return;

	playbackStatus = STOP;

	scheduleSignal(sigPlaybackStatusChanged);
}

void Plugin::playPause()
{
	if(currentTrack < 0)
		throw NoCurrentTrackException();

	if(playbackStatus == PLAY)
		playbackStatus = PAUSE;
	else
		playbackStatus = PLAY;

	scheduleSignal(sigPlaybackStatusChanged);
}

void Plugin::nextTrack()
{
	if(currentTrack < 0)
		throw NoCurrentTrackException();

	if(static_cast<size_t>(currentTrack) + 1 >= playlist.size())
		throw range_error("nextTrack would yield out of bounds");

	++currentTrack;
	playbackStatus = PLAY;

	scheduleSignal(sigPlaybackStatusChanged);
}

void Plugin::prevTrack()
{
	if(currentTrack < 0)
		throw NoCurrentTrackException();

	if(currentTrack == 0)
		throw range_error("prevTrack would yield negative track number");

	--currentTrack;
	playbackStatus = PLAY;

	scheduleSignal(sigPlaybackStatusChanged);
}

int Plugin::getCurrentTrack()
{
	if(currentTrack < 0)
		throw NoCurrentTrackException();

	return currentTrack;
}

boost::posix_time::time_duration Plugin::getElapsed()
{
	if(currentTrack < 0)
		throw NoCurrentTrackException();

	assert(!timeOfLastElapsedUpdate.is_special());

	// Update elapsed
	const boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
	elapsed += now - timeOfLastElapsedUpdate;
	timeOfLastElapsedUpdate = now;

	return elapsed;
}

void Plugin::seek(const boost::posix_time::time_duration& pos)
{
	if(currentTrack < 0)
		throw NoCurrentTrackException();

	if(playbackStatus == STOP)
		throw InvalidPlaybackStateException();

	elapsed = pos;
	timeOfLastElapsedUpdate = boost::posix_time::microsec_clock::local_time();

	scheduleSignal(sigPlaybackStatusChanged);
}

int Plugin::getVolume()
{
	return volume;
}

void Plugin::setVolume(int vol)
{
	if(vol < 0 || vol > 100)
		throw range_error("Invalid volume");

	volume = vol;
	scheduleSignal(sigVolumeChanged);
}

const Plugin::TrackList& Plugin::getTrackList()
{
	return tracks;
}

const Plugin::Playlist& Plugin::getPlaylist()
{
	return playlist;
}

void Plugin::addToPlaylist(const std::string& fileName)
{
	BOOST_FOREACH(Track t, tracks) {
		if(t.getFileName() == fileName) {
			playlist.push_back(t);
			scheduleSignal(sigPlaylistChanged);
			return;
		}
	}

	throw range_error("No such track");
}

void Plugin::removeFromPlaylist(int index)
{
	if(index < 0 || static_cast<size_t>(index) >= playlist.size())
		throw range_error("Illegal index");

	playlist.erase(playlist.begin() + index);
	scheduleSignal(sigPlaylistChanged);
}

void Plugin::clearPlaylist()
{
	playlist.clear();
	scheduleSignal(sigPlaylistChanged);
}

void Plugin::resetElapsed()
{
	elapsed = boost::posix_time::time_duration();
	timeOfLastElapsedUpdate = boost::posix_time::microsec_clock::local_time();
}

void Plugin::scheduleSignal(boost::signals2::signal<void ()>& sig)
{
	boost::unique_lock<boost::mutex> lock(mutex);
	signalsToFire.insert(&sig);
	fireSignals.notify_one();
}

} // namespace stub
} // namespace backend

extern "C" backend::stub::Plugin* create(const boost::property_tree::ptree& config)
{
	// Initial state can be set in config
	// TODO: There are still things that can't be set
	backend::Plugin::TrackList tracks;

	BOOST_FOREACH(const boost::property_tree::ptree::value_type& trackElement, config.get_child("config.backend.stub.tracks")) {
		if(trackElement.first != "track")
			throw runtime_error("Unknown element: config.backend.stub.tracks." + string(trackElement.first));

		Track track;

		BOOST_FOREACH(const boost::property_tree::ptree::value_type& v, trackElement.second.get_child("")) {
			if(v.first == "file")
				track.setFileName(v.second.data());
			else if(v.first == "duration")
				track.setDuration(boost::posix_time::seconds(boost::lexical_cast<unsigned int>(v.second.data())));
			else if(v.first == "artist")
				track.setArtist(v.second.data());
			else if(v.first == "album")
				track.setAlbum(v.second.data());
			else if(v.first == "title")
				track.setTitle(v.second.data());
			else
				throw runtime_error("Unknown element: config.backend.stub.tracks.track." + string(v.first));
		}

		tracks.push_back(track);
	}

	return new backend::stub::Plugin(tracks, backend::Plugin::Playlist(), backend::Plugin::STOP, 50);
}

extern "C" void destroy(backend::stub::Plugin* p)
{
	delete p;
}
